/*
	Dispatch.cpp

	Contains the main operating code for the Dispatch object

	Author: Aidan Goddard 9/8/14
*/

#include"Error.h"
#include"Dispatch.h"
#include"EngineState.h"
#include"Engine.h"
#include"Input.h"
#include"SnowstormXGame.h"

#include<strsafe.h>

#define MAX_PERSISTENT_ENTITIES 8192


////////////////////////////////////////////////////////////////
// thread synchronisation stuff

#define DISPATCH_MODE_HOLD 0
#define DISPATCH_MODE_INPUTEVENTS 1
#define DISPATCH_MODE_GAMECODE 2


#define SYNC_SETDISPATCHMODE(mode) Snowstorm::Dispatch::dispatch_mode=(mode);

#define SYNC_WAITFORMAIN(stage) \
	Snowstorm::Dispatch::dispatch_states[thread_ID]=(stage); \
	while(Snowstorm::Dispatch::dispatch_mode==(stage)){}\
	Snowstorm::Dispatch::dispatch_states[thread_ID]=DISPATCH_MODE_HOLD;

#define SYNC_WAITFORTHREADS(stage) \
	for(uint32_t i = 1; i < Snowstorm::Dispatch::total_threads; i++)\
	{\
		while(Snowstorm::Dispatch::dispatch_states[i]!=(stage)){}\
	}\
	Snowstorm::Dispatch::dispatch_mode=DISPATCH_MODE_HOLD;




////////////////////////////////////////////////////////////////
// static data

// thread and sync data
uint32_t Snowstorm::Dispatch::total_threads = 0;
volatile uint32_t Snowstorm::Dispatch::dispatch_mode = DISPATCH_MODE_HOLD;
volatile uint32_t *Snowstorm::Dispatch::dispatch_states = NULL;

// key event lists
uint32_t *Snowstorm::Dispatch::input_key_task_list_base = NULL;
uint32_t *Snowstorm::Dispatch::input_key_task_list_limit = NULL;

// main entity
Snowstorm::Entity *Snowstorm::Dispatch::main_entity;

// persistent entity execution list
Snowstorm::Entity **Snowstorm::Dispatch::persistent_entity_command_queue;
uint32_t Snowstorm::Dispatch::persistent_entity_command_queue_length;
uint32_t Snowstorm::Dispatch::persistent_entity_command_queue_ptr[64];


// persistent entity creation lists
Snowstorm::Entity ***Snowstorm::Dispatch::persistent_entity_creation_queues;
uint32_t *Snowstorm::Dispatch::persistent_entity_creation_counts;
Snowstorm::Entity ***Snowstorm::Dispatch::persistent_entity_deletion_queues;
uint32_t *Snowstorm::Dispatch::persistent_entity_deletion_counts;

////////////////////////////////////////////////////////////////
// init function

void Snowstorm::Dispatch::InitDispatch(int total_threads, Entity *main_entity)
{
	// save thread count
	Snowstorm::Dispatch::total_threads = total_threads;

	// create a task for the main function
	Snowstorm::Dispatch::main_entity = main_entity;

	// init the thread dispatch states
	dispatch_states = new uint32_t[total_threads];
	for(int i = 0; i < total_threads; i++)
	{
		dispatch_states[i] = DISPATCH_MODE_HOLD;
	}

	// create thread task list for key input
	input_key_task_list_base = new uint32_t[total_threads];
	input_key_task_list_limit = new uint32_t[total_threads];

	// allocate key ranges for threads
	uint32_t job_count = 256;
	uint32_t job_separation = job_count / total_threads;
	for(int i = total_threads - 1; i >= 1 ; i--)
	{
		input_key_task_list_limit[i] = job_count;
		job_count -= job_separation;
		input_key_task_list_base[i] = job_count;
	}
	input_key_task_list_limit[0] = job_count;
	input_key_task_list_base[0] = 0;

	// initialise persistent entity list & command queues
	persistent_entity_command_queue = new Snowstorm::Entity*[MAX_PERSISTENT_ENTITIES];
	persistent_entity_command_queue_length = 0;
	persistent_entity_command_queue_ptr[32] = 0;

	// initialise persistent entity creation queues & deletion queues
	persistent_entity_creation_queues = new Snowstorm::Entity**[total_threads];
	persistent_entity_creation_counts = new uint32_t[total_threads];
	persistent_entity_deletion_queues = new Snowstorm::Entity**[total_threads];
	persistent_entity_deletion_counts = new uint32_t[total_threads];
	for(int i = 0; i < total_threads; i++)
	{
		persistent_entity_creation_counts[i] = 0;
		persistent_entity_creation_queues[i] = new Snowstorm::Entity*[MAX_PERSISTENT_ENTITIES];
		persistent_entity_deletion_counts[i] = 0;
		persistent_entity_deletion_queues[i] = new Snowstorm::Entity*[MAX_PERSISTENT_ENTITIES];
	}

	// finished
	return;
}

//////////////////////////////////////////////////////////////////
// worker dispatch function

void Snowstorm::Dispatch::WorkerDispatchTask(int thread_ID)
{
	// check which mode we're in
	switch(dispatch_mode)
	{
	case DISPATCH_MODE_HOLD:
		dispatch_states[thread_ID] = DISPATCH_MODE_HOLD;
		return;

	case DISPATCH_MODE_INPUTEVENTS:
		// poll the keyboard events assigned to this thread
		Snowstorm::Input::PollKeyboardInput(
			input_key_task_list_base[thread_ID],
			input_key_task_list_limit[thread_ID],
			thread_ID);

		// wait for the main thread to signal stage completion
		SYNC_WAITFORMAIN(DISPATCH_MODE_INPUTEVENTS)
		return;

	case DISPATCH_MODE_GAMECODE:
		// dispatch persistent entity actions
		if(Snowstorm::Engine::ev->actions_paused < PAUSE_STATE_ALL)
		{
			// perform the persistent entity actions
			while(1)
			{
				// get the pointer & increase it for the next fetch
				uint32_t ptr = InterlockedIncrement(&(persistent_entity_command_queue_ptr[32])) - 1;

				// check if all persistent entity tasks have been performed this frame
				if(ptr >= persistent_entity_command_queue_length)
				{
					// all persistent entity tasks have been performed this frame
					break;
				}

				// perform the task
				Entity *e = persistent_entity_command_queue[ptr];
				e->Resume(thread_ID);

				// check if it has exited
				if(!e->executing)
				{
					// mark this entity for deletion
					RemovePersistentEntity(thread_ID, e);
					EngineState::engine_state.cmd_delete_persistent_entity = true;
				}

			}
		}

		// dispatch map entity actions
		if(Snowstorm::Engine::ev->actions_paused < PAUSE_STATE_MAP)
		{
	
		}

		// wait for the main thread to signal stage completion
		SYNC_WAITFORMAIN(DISPATCH_MODE_GAMECODE)
		return;

	}

	// finished
	return;
}

/////////////////////////////////////////////////////////////////
// main thread signal functions

void Snowstorm::Dispatch::SignalInputEventDispatch()
{
	// perform xbox controller & mouse poll
	Snowstorm::Input::PollXboxControllerInput();

	// set dispatcher to keyboard polling
	SYNC_SETDISPATCHMODE(DISPATCH_MODE_INPUTEVENTS)

	// perform main thread's keyboard polling
	Snowstorm::Input::PollKeyboardInput(
		Snowstorm::Dispatch::input_key_task_list_base[0],
		Snowstorm::Dispatch::input_key_task_list_limit[0],
		0);	// called from the master thread

	// wait for all other workers to finish
	SYNC_WAITFORTHREADS(DISPATCH_MODE_INPUTEVENTS)

	// finished
	return;
}

void Snowstorm::Dispatch::SignalGameCodeDispatch()
{
	// call the main function
	main_entity->Resume(0);

	// check it didn't exit
	if(!main_entity->executing)
	{
		// it did exit
		// tell the engine to stop after this frame
		EngineState::engine_state.cmd_exit = true;
		EngineState::engine_state.exit_return_code = main_entity->GetActionReturnCode();
		return;
	}

	// reset pointers
	persistent_entity_command_queue_ptr[32] = 0;

	// set the worker threads to game code mode
	SYNC_SETDISPATCHMODE(DISPATCH_MODE_GAMECODE)
	
	// dispatch persistent entity actions
	if(Engine::ev->actions_paused < PAUSE_STATE_ALL)
	{
		// perform the persistent entity actions
		while(1)
		{
			// get the pointer & increase it for the next fetch
			uint32_t ptr = InterlockedIncrement(&(persistent_entity_command_queue_ptr[32])) - 1;

			// check if all persistent entity tasks have been performed this frame
			if(ptr >= persistent_entity_command_queue_length)
			{
				// all persistent entity tasks have been performed this frame
				break;
			}

			// perform the task
			Entity *e = persistent_entity_command_queue[ptr];
			e->Resume(0);

			// check if it has exited
			if(!e->executing)
			{
				// mark this entity for deletion
				RemovePersistentEntity(0, e);
				EngineState::engine_state.cmd_delete_persistent_entity = true;
			}

		}
	}

	// dispatch map entity actions
	if(Engine::ev->actions_paused < PAUSE_STATE_MAP)
	{

	}

	// wait for all other workers to finish
	SYNC_WAITFORTHREADS(DISPATCH_MODE_GAMECODE)

	// finished
	return;
}

//////////////////////////////////////////////////////////////////////
// persistent entity creation/deletion functions

void Snowstorm::Dispatch::InsertNewPersistentEntity(uint32_t thread_id, Entity *entity)
{
	// get pointer to new entity queue
	Entity **queue = persistent_entity_creation_queues[thread_id];
	
	// check that the queue is not full
	if(persistent_entity_creation_counts[thread_id] >= MAX_PERSISTENT_ENTITIES)
	{
		// queue is full, signal error
		wchar_t msg[512];
		StringCbPrintf(msg, 512, L"ERROR: Could not allocate space to create persistent entity\n \"%s : %s %u %u\"\n on thread %u", 
			entity->name,
			entity->sub_name,
			entity->id_1,
			entity->id_2,
			thread_id);
		Error::SignalError(L"Persistent Entity Error", msg);
	}

	// add entity to the queue and update queue length
	queue[persistent_entity_creation_counts[thread_id]++] = entity;

	// finished
	return;
}


void Snowstorm::Dispatch::RemovePersistentEntity(uint32_t thread_id, Entity *entity)
{
	// get pointer to delete entity queue
	Entity **queue = persistent_entity_deletion_queues[thread_id];

	// check that the queue is not full
	if(persistent_entity_deletion_counts[thread_id] >= MAX_PERSISTENT_ENTITIES)
	{
		// queue is full, signal error
		wchar_t msg[512];
		StringCbPrintf(msg, 512, L"ERROR: Could not allocate space to send delete persistent entity command\n \"%s : %s %u %u\"\n on thread %u", 
			entity->name,
			entity->sub_name,
			entity->id_1,
			entity->id_2,
			thread_id);
		Error::SignalError(L"Persistent Entity Error", msg);
	}

	// add entity to the queue and update queue length
	queue[persistent_entity_deletion_counts[thread_id]++] = entity;

	// finished
	return;
}

void Snowstorm::Dispatch::UpdatePersistentEntities()
{
	// go through each thread's delete persistent entity queue
	for(uint32_t i = 0; i < total_threads; i++)
	{
		// for each entity in this thread's queue
		for(uint32_t k = 0; k < persistent_entity_deletion_counts[i]; k++)
		{
			// get ptr of entity to remove
			Entity *e = Dispatch::persistent_entity_deletion_queues[i][k];

			// find it in the command-queue
			for(uint32_t j = 0; j < persistent_entity_command_queue_length; j++)
			{
				// check if it is this one
				if(persistent_entity_command_queue[j] == e)
				{
					// found it, now shift everything after it forwards a position
					for(uint32_t l = j + 1; l < persistent_entity_command_queue_length; l++)
					{
						persistent_entity_command_queue[l - 1] = persistent_entity_command_queue[l];
					}

					// decrease the queue length
					persistent_entity_command_queue_length--;
					break;
				}
			}
		}

		// reset that thread's queue length
		persistent_entity_deletion_counts[i] = 0;
	}

	// go through each thread's new persistent entity queue
	for(uint32_t i = 0; i < total_threads; i++)
	{
		// for each entity in this thread's queue
		for(uint32_t k = 0; k < persistent_entity_creation_counts[i]; k++)
		{
			// check if the persistent entity list is not full
			if(persistent_entity_command_queue_length >= MAX_PERSISTENT_ENTITIES)
			{
				// error-out
				wchar_t msg[512];
				StringCbPrintf(msg, 512, L"ERROR: Could not create persistent entity\n \"%s : %s %u %u\"\nPersistent entity list is full", 
					Dispatch::persistent_entity_creation_queues[i][k]->name,
					Dispatch::persistent_entity_creation_queues[i][k]->sub_name,
					Dispatch::persistent_entity_creation_queues[i][k]->id_1,
					Dispatch::persistent_entity_creation_queues[i][k]->id_2);
				Error::SignalError(L"Persistent Entity Error", msg);
			}

			// place the new persistent entity in the queue
			persistent_entity_command_queue[persistent_entity_command_queue_length++] = persistent_entity_creation_queues[i][k];
		}

		// reset that thread's queue length
		persistent_entity_creation_counts[i] = 0;
	}


	// finished
	return;
}